package com.czx.drivingtest.aspect;

import com.czx.drivingtest.annotation.CustomizeCache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author czx
 * @version 2020/9/20 6:20 下午
 * @since JDK12
 */
@Component
@Aspect
public class CacheAspect {
//    private static Logger logger = LoggerFactory.getLogger(CacheAspect.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Pointcut("@annotation(com.czx.drivingtest.annotation.CustomizeCache)")
    public void cachePointcut() { }

    @Around("cachePointcut()")
    public Object doCache(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        //获取方法上注解的类容
        Method method = point.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
        CustomizeCache annotation = method.getAnnotation(CustomizeCache.class);
        String keyEl = annotation.key();
        String prefix = annotation.value();
        StandardEvaluationContext context = createStandardEvaluationContext(method, point.getArgs());
        // 拼接redis的key
        String key = prefix + "::" + parseKeyElValue(keyEl, context);

        //判断缓存中是否存在
        Object value = redisTemplate.opsForValue().get(key);
        if (value != null) {
//            logger.info("从缓存中读取到值：{}", value);
            return value;
        }
        //自定义组件，如：限流，降级。。。
        //创建限流令牌
        Semaphore semaphore = null;
        try {
            semaphore = new Semaphore(annotation.semaphoreCount());
            boolean tryAcquire = semaphore.tryAcquire(3000L, TimeUnit.MILLISECONDS);
            if (!tryAcquire) {
                //log.info("当前线程【{}】获取令牌失败,等带其他线程释放令牌", Thread.currentThread().getName());
                throw new RuntimeException(String.format("当前线程【%s】获取令牌失败,等带其他线程释放令牌", Thread.currentThread().getName()));
            }
            //缓存不存在则执行方法
            value = point.proceed();
            //同步value到缓存
            if (value != null) {
                redisTemplate.opsForValue().set(key, value, annotation.expireTimes(), TimeUnit.SECONDS);
            }
            return value;
        } finally {
            if (semaphore != null) {
                semaphore.release();
            }
        }
    }

    /**
     * 解析EL表达式对应的值
     * @param keyEl
     * @param context
     * @return
     */
    private Object parseKeyElValue(String keyEl, StandardEvaluationContext context) {
        //创建EL表达式解析器
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(keyEl);
        return expression.getValue(context);
    }

    /**
     * 创建方法的参数信息上下文
     * @param method
     * @param args 方法的参数
     * @return
     */
    private StandardEvaluationContext createStandardEvaluationContext(Method method, Object[] args) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i].toString());
        }
        return context;
    }

}
